/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.shardingsphere.infra.algorithm.messagedigest.sm3;

import org.apache.commons.codec.binary.Hex;
import org.apache.shardingsphere.infra.algorithm.core.exception.AlgorithmInitializationException;
import org.apache.shardingsphere.infra.algorithm.messagedigest.core.MessageDigestAlgorithm;
import org.apache.shardingsphere.infra.exception.core.ShardingSpherePreconditions;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.nio.charset.StandardCharsets;
import java.security.Security;
import java.util.Properties;

/**
 * SM3 message digest algorithm.
 */
public final class SM3MessageDigestAlgorithm implements MessageDigestAlgorithm {
    
    private static final String SM3_SALT = "sm3-salt";
    
    private static final int SALT_LENGTH = 8;
    
    private byte[] sm3Salt;
    
    static {
        Security.addProvider(new BouncyCastleProvider());
    }
    
    @Override
    public void init(final Properties props) {
        sm3Salt = getSalt(props);
    }
    
    private byte[] getSalt(final Properties props) {
        String salt = props.getProperty(SM3_SALT, "");
        ShardingSpherePreconditions.checkState(salt.isEmpty() || SALT_LENGTH == salt.length(),
                () -> new AlgorithmInitializationException(this, "Salt should be either blank or better " + SALT_LENGTH + " bytes long."));
        return salt.isEmpty() ? new byte[0] : salt.getBytes(StandardCharsets.UTF_8);
    }
    
    @Override
    public String digest(final Object plainValue) {
        return null == plainValue ? null : Hex.encodeHexString(digest(String.valueOf(plainValue).getBytes(StandardCharsets.UTF_8), sm3Salt));
    }
    
    private byte[] digest(final byte[] input, final byte[] salt) {
        SM3Digest sm3Digest = new SM3Digest();
        byte[] updateByte = concat(input, salt);
        sm3Digest.update(updateByte, 0, updateByte.length);
        byte[] result = new byte[sm3Digest.getDigestSize()];
        sm3Digest.doFinal(result, 0);
        return result;
    }
    
    private byte[] concat(final byte[] input, final byte[] salt) {
        int inputLength = input.length;
        int saltLength = salt.length;
        byte[] result = new byte[inputLength + saltLength];
        System.arraycopy(input, 0, result, 0, inputLength);
        System.arraycopy(salt, 0, result, inputLength, saltLength);
        return result;
    }
    
    @Override
    public String getType() {
        return "SM3";
    }
}
